/* SPDX-License-Identifier: Apache-2.0 */
/* Copyright 2015-2025 Espressif Systems (Shanghai) PTE LTD */

#include "sdkconfig.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "soc/gpio_reg.h"
#include "esp_log.h"
#include "interface.h"
#include "driver/spi_slave.h"
#include "driver/gpio.h"
#include "endian.h"
#include "freertos/task.h"
#include "mempool.h"
#include "stats.h"
#include "esp_timer.h"
#include "esp_hosted_interface.h"
#include "esp_hosted_transport.h"
#include "esp_hosted_transport_init.h"
#include "esp_hosted_header.h"
#include "host_power_save.h"
#include "esp_hosted_interface.h"
#include "slave_wifi_config.h"
#include "esp_hosted_coprocessor_fw_ver.h"

static const char TAG[] = "SPI_DRIVER";
/* SPI settings */
#define SPI_BITS_PER_WORD          8

#define ESP_SPI_MODE               CONFIG_ESP_SPI_MODE
#define GPIO_MOSI                  CONFIG_ESP_SPI_GPIO_MOSI
#define GPIO_MISO                  CONFIG_ESP_SPI_GPIO_MISO
#define GPIO_SCLK                  CONFIG_ESP_SPI_GPIO_CLK
#define GPIO_CS                    CONFIG_ESP_SPI_GPIO_CS
#define GPIO_DATA_READY            CONFIG_ESP_SPI_GPIO_DATA_READY
#define GPIO_HANDSHAKE             CONFIG_ESP_SPI_GPIO_HANDSHAKE

#define ESP_SPI_CONTROLLER         CONFIG_ESP_SPI_CONTROLLER

#define SPI_RX_QUEUE_SIZE          CONFIG_ESP_SPI_RX_Q_SIZE
#define SPI_TX_QUEUE_SIZE          CONFIG_ESP_SPI_TX_Q_SIZE

// de-assert HS signal on CS, instead of at end of transaction
#if defined(CONFIG_ESP_SPI_DEASSERT_HS_ON_CS)
#define HS_DEASSERT_ON_CS (1)
#else
#define HS_DEASSERT_ON_CS (0)
#endif

/* By default both Handshake and Data Ready used Active High,
 * unless configured otherwise.
 * For Active low, set value as 0 */
#define H_HANDSHAKE_ACTIVE_HIGH    1
#define H_DATAREADY_ACTIVE_HIGH    1

/* SPI-DMA settings */
#define SPI_DMA_ALIGNMENT_BYTES    4
#define SPI_DMA_ALIGNMENT_MASK     (SPI_DMA_ALIGNMENT_BYTES-1)
#define IS_SPI_DMA_ALIGNED(VAL)    (!((VAL)& SPI_DMA_ALIGNMENT_MASK))
#define MAKE_SPI_DMA_ALIGNED(VAL)  (VAL += SPI_DMA_ALIGNMENT_BYTES - \
				((VAL)& SPI_DMA_ALIGNMENT_MASK))

#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)
    #define DMA_CHAN               ESP_SPI_CONTROLLER
#else
    #define DMA_CHAN               SPI_DMA_CH_AUTO
#endif
static uint8_t hosted_constructs_init_done = 0;


#if ESP_SPI_MODE==0
#  error "SPI mode 0 at SLAVE is NOT supported"
#endif
/* SPI internal configs */
#define SPI_BUFFER_SIZE            MAX_TRANSPORT_BUF_SIZE
#define SPI_QUEUE_SIZE             3

#define GPIO_MASK_DATA_READY (1ULL << GPIO_DATA_READY)
#define GPIO_MASK_HANDSHAKE (1ULL << GPIO_HANDSHAKE)

#if HS_DEASSERT_ON_CS
#define H_CS_INTR_TO_CLEAR_HS                        GPIO_INTR_ANYEDGE
#else
#define H_CS_INTR_TO_CLEAR_HS                        GPIO_INTR_NEGEDGE
#endif

#if H_HANDSHAKE_ACTIVE_HIGH
  #define H_HS_VAL_ACTIVE                            GPIO_OUT_W1TS_REG
  #define H_HS_VAL_INACTIVE                          GPIO_OUT_W1TC_REG
  #define H_HS_PULL_REGISTER                         GPIO_PULLDOWN_ONLY
#else
  #define H_HS_VAL_ACTIVE                            GPIO_OUT_W1TC_REG
  #define H_HS_VAL_INACTIVE                          GPIO_OUT_W1TS_REG
  #define H_HS_PULL_REGISTER                         GPIO_PULLUP_ONLY
#endif

#if H_DATAREADY_ACTIVE_HIGH
  #define H_DR_VAL_ACTIVE                            GPIO_OUT_W1TS_REG
  #define H_DR_VAL_INACTIVE                          GPIO_OUT_W1TC_REG
  #define H_DR_PULL_REGISTER                         GPIO_PULLDOWN_ONLY
#else
  #define H_DR_VAL_ACTIVE                            GPIO_OUT_W1TC_REG
  #define H_DR_VAL_INACTIVE                          GPIO_OUT_W1TS_REG
  #define H_DR_PULL_REGISTER                         GPIO_PULLUP_ONLY
#endif

static interface_context_t context;
static interface_handle_t if_handle_g;
static SemaphoreHandle_t spi_tx_sem;
static SemaphoreHandle_t spi_rx_sem;
#if HS_DEASSERT_ON_CS
static SemaphoreHandle_t wait_cs_deassert_sem;
#endif
static QueueHandle_t spi_rx_queue[MAX_PRIORITY_QUEUES];
static QueueHandle_t spi_tx_queue[MAX_PRIORITY_QUEUES];

static interface_handle_t * esp_spi_init(void);
static int32_t esp_spi_write(interface_handle_t *handle,
				interface_buffer_handle_t *buf_handle);
static int esp_spi_read(interface_handle_t *if_handle, interface_buffer_handle_t * buf_handle);
static esp_err_t esp_spi_reset(interface_handle_t *handle);
static void esp_spi_deinit(interface_handle_t *handle);
static void esp_spi_read_done(void *handle);
static void queue_next_transaction(void);

if_ops_t if_ops = {
	.init = esp_spi_init,
	.write = esp_spi_write,
	.read = esp_spi_read,
	.reset = esp_spi_reset,
	.deinit = esp_spi_deinit,
};

#define SPI_MEMPOOL_NUM_BLOCKS     ((SPI_TX_QUEUE_SIZE+SPI_RX_QUEUE_SIZE)+SPI_QUEUE_SIZE*2)
static struct hosted_mempool * buf_mp_tx_g;
static struct hosted_mempool * buf_mp_rx_g;
static struct hosted_mempool * trans_mp_g;

static inline void spi_mempool_create(void)
{
	buf_mp_tx_g = hosted_mempool_create(NULL, 0,
			SPI_MEMPOOL_NUM_BLOCKS, SPI_BUFFER_SIZE);
	/* re-use the mempool, as same size, can be seperate, if needed */
	buf_mp_rx_g = buf_mp_tx_g;
	trans_mp_g = hosted_mempool_create(NULL, 0,
			SPI_MEMPOOL_NUM_BLOCKS, sizeof(spi_slave_transaction_t));
#if CONFIG_ESP_CACHE_MALLOC
	assert(buf_mp_tx_g);
	assert(buf_mp_rx_g);
	assert(trans_mp_g);
#else
	ESP_LOGI(TAG, "Using dynamic heap for mem alloc");
#endif
}

static inline void spi_mempool_destroy(void)
{
	hosted_mempool_destroy(buf_mp_tx_g);
	hosted_mempool_destroy(trans_mp_g);
}

static inline void *spi_buffer_tx_alloc(uint need_memset)
{
	return hosted_mempool_alloc(buf_mp_tx_g, SPI_BUFFER_SIZE, need_memset);
}

static inline void *spi_buffer_rx_alloc(uint need_memset)
{
	return hosted_mempool_alloc(buf_mp_rx_g, SPI_BUFFER_SIZE, need_memset);
}

static inline spi_slave_transaction_t *spi_trans_alloc(uint need_memset)
{
	return hosted_mempool_alloc(trans_mp_g, sizeof(spi_slave_transaction_t), need_memset);
}

static inline void spi_buffer_tx_free(void *buf)
{
	hosted_mempool_free(buf_mp_tx_g, buf);
}

static inline void spi_buffer_rx_free(void *buf)
{
	hosted_mempool_free(buf_mp_rx_g, buf);
}

static inline void spi_trans_free(spi_slave_transaction_t *trans)
{
	hosted_mempool_free(trans_mp_g, trans);
}
volatile uint8_t data_ready_flag = 0;
#define set_handshake_gpio()     ESP_EARLY_LOGD(TAG, "+ set handshake gpio");gpio_set_level(GPIO_HANDSHAKE, 1);
#define reset_handshake_gpio()   ESP_EARLY_LOGD(TAG, "- reset handshake gpio");gpio_set_level(GPIO_HANDSHAKE, 0);
#define set_dataready_gpio()     if (!data_ready_flag) {ESP_EARLY_LOGD(TAG, "+ set dataready gpio");gpio_set_level(GPIO_DATA_READY, 1);data_ready_flag = 1;}
#define reset_dataready_gpio()   if (data_ready_flag) {ESP_EARLY_LOGD(TAG, "- reset dataready gpio");gpio_set_level(GPIO_DATA_READY, 0);data_ready_flag = 0;}

interface_context_t *interface_insert_driver(int (*event_handler)(uint8_t val))
{
	ESP_LOGI(TAG, "Using SPI interface");
	memset(&context, 0, sizeof(context));

	context.type = SPI;
	context.if_ops = &if_ops;
	context.event_handler = event_handler;

	return &context;
}

int interface_remove_driver()
{
	memset(&context, 0, sizeof(context));
	return 0;
}


static inline int find_wifi_tx_throttling_to_be_set(void)
{
	uint16_t queue_load;
	uint8_t load_percent;

	if (!slv_cfg_g.throttle_high_threshold) {
		/* No high threshold set, no throttlling */
		return 0;
	}

	queue_load = uxQueueMessagesWaiting(spi_rx_queue[PRIO_Q_OTHERS]);

	load_percent = (queue_load*100/SPI_RX_QUEUE_SIZE);

	if (load_percent > slv_cfg_g.throttle_high_threshold) {
		slv_state_g.current_throttling = 1;
		ESP_LOGV(TAG, "throttling started");
#if ESP_PKT_STATS
		pkt_stats.sta_flowctrl_on++;
#endif
	}

	if (load_percent < slv_cfg_g.throttle_low_threshold) {
		slv_state_g.current_throttling = 0;
		ESP_LOGV(TAG, "throttling stopped");
#if ESP_PKT_STATS
		pkt_stats.sta_flowctrl_off++;
#endif
	}

	return slv_state_g.current_throttling;
}


void generate_startup_event(uint8_t cap, uint32_t ext_cap)
{
	struct esp_payload_header *header = NULL;
	interface_buffer_handle_t buf_handle = {0};
	struct esp_priv_event *event = NULL;
	uint8_t *pos = NULL;
	uint16_t len = 0;
	uint8_t raw_tp_cap = 0;
	uint32_t total_len = 0;

	buf_handle.payload = spi_buffer_tx_alloc(MEMSET_REQUIRED);

	raw_tp_cap = debug_get_raw_tp_conf();

	assert(buf_handle.payload);
	header = (struct esp_payload_header *) buf_handle.payload;

	header->if_type = ESP_PRIV_IF;
	header->if_num = 0;
	header->offset = htole16(sizeof(struct esp_payload_header));
	header->priv_pkt_type = ESP_PACKET_TYPE_EVENT;
	header->throttle_cmd = 0;

	/* Populate event data */
	event = (struct esp_priv_event *) (buf_handle.payload + sizeof(struct esp_payload_header));

	event->event_type = ESP_PRIV_EVENT_INIT;

	/* Populate TLVs for event */
	pos = event->event_data;

	/* TLVs start */

	/* TLV - Board type */
	ESP_LOGI(TAG, "Slave chip Id[%x]", ESP_PRIV_FIRMWARE_CHIP_ID);
	*pos = ESP_PRIV_FIRMWARE_CHIP_ID;   pos++;len++;
	*pos = LENGTH_1_BYTE;               pos++;len++;
	*pos = CONFIG_IDF_FIRMWARE_CHIP_ID; pos++;len++;

	/* TLV - Capability */
	*pos = ESP_PRIV_CAPABILITY;         pos++;len++;
	*pos = LENGTH_1_BYTE;               pos++;len++;
	*pos = cap;                         pos++;len++;

	*pos = ESP_PRIV_TEST_RAW_TP;        pos++;len++;
	*pos = LENGTH_1_BYTE;               pos++;len++;
	*pos = raw_tp_cap;                  pos++;len++;

	*pos = ESP_PRIV_RX_Q_SIZE;          pos++;len++;
	*pos = LENGTH_1_BYTE;               pos++;len++;
	*pos = SPI_RX_QUEUE_SIZE;           pos++;len++;

	*pos = ESP_PRIV_TX_Q_SIZE;          pos++;len++;
	*pos = LENGTH_1_BYTE;               pos++;len++;
	*pos = SPI_TX_QUEUE_SIZE;           pos++;len++;

	// convert fw version into a uint32_t
	uint32_t fw_version = ESP_HOSTED_VERSION_VAL(PROJECT_VERSION_MAJOR_1,
			PROJECT_VERSION_MINOR_1,
			PROJECT_VERSION_PATCH_1);

	// send fw version as a little-endian uint32_t
	*pos = ESP_PRIV_FIRMWARE_VERSION;   pos++;len++;
	*pos = LENGTH_4_BYTE;               pos++;len++;
	// send fw_version as a little endian 32bit value
	*pos = (fw_version & 0xff);         pos++;len++;
	*pos = (fw_version >> 8) & 0xff;    pos++;len++;
	*pos = (fw_version >> 16) & 0xff;   pos++;len++;
	*pos = (fw_version >> 24) & 0xff;   pos++;len++;

	/* TLVs end */

	event->event_len = len;

	/* payload len = Event len + sizeof(event type) + sizeof(event len) */
	len += 2;
	header->len = htole16(len);

	total_len = len + sizeof(struct esp_payload_header);

	if (!IS_SPI_DMA_ALIGNED(total_len)) {
		MAKE_SPI_DMA_ALIGNED(total_len);
	}

	buf_handle.payload_len = total_len;

#if CONFIG_ESP_SPI_CHECKSUM
	header->checksum = htole16(compute_checksum(buf_handle.payload, len + sizeof(struct esp_payload_header)));
#endif

	xQueueSend(spi_tx_queue[PRIO_Q_OTHERS], &buf_handle, portMAX_DELAY);
	xSemaphoreGive(spi_tx_sem);

	set_dataready_gpio();
	/* process first data packet here to start transactions */
	queue_next_transaction();
}


/* Invoked after transaction is queued and ready for pickup by master */
static void IRAM_ATTR spi_post_setup_cb(spi_slave_transaction_t *trans)
{
	/* ESP peripheral ready for spi transaction. Set hadnshake line high. */
	set_handshake_gpio();
}

/* Invoked after transaction is sent/received.
 * Use this to set the handshake line low */
static void IRAM_ATTR spi_post_trans_cb(spi_slave_transaction_t *trans)
{
#if !HS_DEASSERT_ON_CS
	/* Clear handshake line */
	reset_handshake_gpio();
#endif
}

static uint8_t * get_next_tx_buffer(uint32_t *len)
{
	interface_buffer_handle_t buf_handle = {0};
	esp_err_t ret = ESP_OK;
	uint8_t *sendbuf = NULL;
	struct esp_payload_header *header = NULL;

	/* Get or create new tx_buffer
	 *	1. Check if SPI TX queue has pending buffers. Return if valid buffer is obtained.
	 *	2. Create a new empty tx buffer and return */

	/* Get buffer from SPI Tx queue */
	ret = xSemaphoreTake(spi_tx_sem, 0);
	if (pdTRUE == ret)
		if (pdFALSE == xQueueReceive(spi_tx_queue[PRIO_Q_SERIAL], &buf_handle, 0))
			if (pdFALSE == xQueueReceive(spi_tx_queue[PRIO_Q_BT], &buf_handle, 0))
				if (pdFALSE == xQueueReceive(spi_tx_queue[PRIO_Q_OTHERS], &buf_handle, 0))
					ret = pdFALSE;

	if (ret == pdTRUE && buf_handle.payload) {
		if (len) {
#if ESP_PKT_STATS
			if (buf_handle.if_type == ESP_SERIAL_IF)
				pkt_stats.serial_tx_total++;
#endif
			*len = buf_handle.payload_len;
		}
		/* Return real data buffer from queue */
		return buf_handle.payload;
	}

	/* No real data pending, clear ready line and indicate host an idle state */
	reset_dataready_gpio();

	/* Create empty dummy buffer */
	sendbuf = spi_buffer_tx_alloc(MEMSET_REQUIRED);
	if (!sendbuf) {
		ESP_LOGE(TAG, "Failed to allocate memory for dummy transaction");
		if (len)
			*len = 0;
		return NULL;
	}

	/* Initialize header */
	header = (struct esp_payload_header *) sendbuf;

	/* Populate header to indicate it as a dummy buffer */
	header->if_type = ESP_MAX_IF;
	header->if_num = 0xF;
	header->len = 0;
	header->throttle_cmd = find_wifi_tx_throttling_to_be_set();

	if (len)
		*len = 0;

	return sendbuf;
}

static int process_spi_rx(interface_buffer_handle_t *buf_handle)
{
	struct esp_payload_header *header = NULL;
	uint16_t len = 0, offset = 0;
	uint8_t flags = 0;
#if CONFIG_ESP_SPI_CHECKSUM
	uint16_t rx_checksum = 0, checksum = 0;
#endif

	/* Validate received buffer. Drop invalid buffer. */

	if (!buf_handle || !buf_handle->payload) {
		ESP_LOGE(TAG, "%s: Invalid params", __func__);
		return -1;
	}

	header = (struct esp_payload_header *) buf_handle->payload;
	len = le16toh(header->len);
	offset = le16toh(header->offset);
	flags = header->flags;
	ESP_LOGV(TAG, "process_spi_rx: flags[%u]", flags);

	if (flags & FLAG_POWER_SAVE_STARTED) {
		ESP_LOGI(TAG, "Host informed starting to power sleep");
		if (context.event_handler) {
			context.event_handler(ESP_POWER_SAVE_ON);
		}
	} else if (flags & FLAG_POWER_SAVE_STOPPED) {
		ESP_LOGI(TAG, "Host informed that it waken up");
		if (context.event_handler) {
			context.event_handler(ESP_POWER_SAVE_OFF);
		}
	}

	if (!len) {
		ESP_LOGV(TAG, "rx_pkt len[%u] is 0, dropping it", len);
		return -1;
	}

	if ((len+offset) > SPI_BUFFER_SIZE) {
		ESP_LOGE(TAG, "rx_pkt len+offset[%u]>max[%u], dropping it", len+offset, SPI_BUFFER_SIZE);

		return -1;
	}

#if CONFIG_ESP_SPI_CHECKSUM
	rx_checksum = le16toh(header->checksum);
	header->checksum = 0;

	checksum = compute_checksum(buf_handle->payload, len+offset);

	if (checksum != rx_checksum) {
		ESP_LOGE(TAG, "%s: cal_chksum[%u] != exp_chksum[%u], drop len[%u] offset[%u]",
				__func__, checksum, rx_checksum, len, offset);
		return -1;
	}
#endif
	ESP_HEXLOGD("spi_rx", header, len, 32);

	/* Buffer is valid */
	buf_handle->if_type = header->if_type;
	buf_handle->if_num = header->if_num;
	buf_handle->free_buf_handle = esp_spi_read_done;
	buf_handle->payload_len = len + offset;
	buf_handle->priv_buffer_handle = buf_handle->payload;


#if ESP_PKT_STATS
	if (buf_handle->if_type == ESP_STA_IF)
		pkt_stats.hs_bus_sta_in++;
#endif
	if (header->if_type == ESP_SERIAL_IF) {
		xQueueSend(spi_rx_queue[PRIO_Q_SERIAL], buf_handle, portMAX_DELAY);
	} else if (header->if_type == ESP_HCI_IF) {
		xQueueSend(spi_rx_queue[PRIO_Q_BT], buf_handle, portMAX_DELAY);
	} else {
		xQueueSend(spi_rx_queue[PRIO_Q_OTHERS], buf_handle, portMAX_DELAY);
	}

	xSemaphoreGive(spi_rx_sem);
	return 0;
}

static void queue_next_transaction(void)
{
	spi_slave_transaction_t *spi_trans = NULL;
	uint32_t len = 0;
	uint8_t *tx_buffer = get_next_tx_buffer(&len);
	if (unlikely(!tx_buffer)) {
		/* Queue next transaction failed */
		ESP_LOGE(TAG , "Failed to queue new transaction\r\n");
		return;
	}
	ESP_HEXLOGD("spi_tx", tx_buffer, len, 32);

	spi_trans = spi_trans_alloc(MEMSET_REQUIRED);
	if (unlikely(!spi_trans)) {
		assert(spi_trans);
	}

	/* Attach Rx Buffer */
	spi_trans->rx_buffer = spi_buffer_rx_alloc(MEMSET_REQUIRED);
	if (unlikely(!spi_trans->rx_buffer)) {
		assert(spi_trans->rx_buffer);
	}

	/* Attach Tx Buffer */
	spi_trans->tx_buffer = tx_buffer;

	/* Transaction len */
	spi_trans->length = SPI_BUFFER_SIZE * SPI_BITS_PER_WORD;

	spi_slave_queue_trans(ESP_SPI_CONTROLLER, spi_trans, portMAX_DELAY);
}

static void spi_transaction_post_process_task(void* pvParameters)
{
	spi_slave_transaction_t *spi_trans = NULL;
	esp_err_t ret = ESP_OK;
	interface_buffer_handle_t rx_buf_handle;

	ESP_LOGI(TAG, "SPI post process task started");
	for (;;) {
		/* Check if interface is being deinitialized */
#if H_PS_UNLOAD_BUS_WHILE_PS
		if (if_handle_g.state == DEINIT) {
			vTaskDelay(pdMS_TO_TICKS(10));
			ESP_LOGI(TAG, "spi deinit");
			continue;
		}
#endif

		memset(&rx_buf_handle, 0, sizeof(rx_buf_handle));

		/* Await transmission result, after any kind of transmission a new packet
		 * (dummy or real) must be placed in SPI slave
		 */
		ESP_ERROR_CHECK(spi_slave_get_trans_result(ESP_SPI_CONTROLLER, &spi_trans,
				portMAX_DELAY));

#if HS_DEASSERT_ON_CS
		/* Wait until CS has been deasserted before we queue a new transaction.
		 *
		 * Some MCUs delay deasserting CS at the end of a transaction.
		 * If we queue a new transaction without waiting for CS to deassert,
		 * the slave SPI can start (since CS is still asserted), and data is lost
		 * as host is not expecting any data.
		 */
		xSemaphoreTake(wait_cs_deassert_sem, portMAX_DELAY);
#endif
		/* Queue new transaction to get ready as soon as possible */
		queue_next_transaction();
		assert(spi_trans);
#if ESP_PKT_STATS
		struct esp_payload_header *header =
			(struct esp_payload_header *)spi_trans->tx_buffer;
		if (header->if_type == ESP_STA_IF)
			pkt_stats.sta_sh_out++;
#endif

		/* Free any tx buffer, data is not relevant anymore */
		spi_buffer_tx_free((void *)spi_trans->tx_buffer);
		/* Process received data */
		if (likely(spi_trans->rx_buffer)) {
			rx_buf_handle.payload = spi_trans->rx_buffer;

			ret = process_spi_rx(&rx_buf_handle);

			/* free rx_buffer if process_spi_rx returns an error
			 * In success case it will be freed later */
			if (unlikely(ret)) {
				spi_buffer_rx_free((void *)spi_trans->rx_buffer);
			}
		} else {
			ESP_LOGI(TAG, "no rx_buf");
		}

		/* Free Transfer structure */
		spi_trans_free(spi_trans);
	}
}

static void IRAM_ATTR gpio_disable_hs_isr_handler(void* arg)
{
#if HS_DEASSERT_ON_CS
	int level = gpio_get_level(GPIO_CS);
	if (level == 0) {
		/* CS is asserted, disable HS */
		reset_handshake_gpio();
	} else {
		/* Last transaction complete, populate next one */
		if (wait_cs_deassert_sem)
			xSemaphoreGive(wait_cs_deassert_sem);
	}
#else
	reset_handshake_gpio();
#endif
}

static void register_hs_disable_pin(uint32_t gpio_num)
{
	if (gpio_num != -1) {
		gpio_reset_pin(gpio_num);

		gpio_config_t slave_disable_hs_pin_conf={
			.intr_type=GPIO_INTR_DISABLE,
			.mode=GPIO_MODE_INPUT,
			.pin_bit_mask=(1ULL<<gpio_num)
		};
		slave_disable_hs_pin_conf.pull_up_en = 1;
		gpio_config(&slave_disable_hs_pin_conf);
		gpio_set_intr_type(gpio_num, H_CS_INTR_TO_CLEAR_HS);
		gpio_install_isr_service(0);
		gpio_isr_handler_add(gpio_num, gpio_disable_hs_isr_handler, NULL);
	}
}

static interface_handle_t * esp_spi_init(void)
{
	if (unlikely(if_handle_g.state >= DEACTIVE)) {
		return &if_handle_g;
	}

#if H_PS_UNLOAD_BUS_WHILE_PS
	if (hosted_constructs_init_done) {

  #if H_IF_AVAILABLE_SPI_SLAVE_ENABLE_DISABLE
		spi_slave_enable(ESP_SPI_CONTROLLER);
  #endif
		if_handle_g.state = ACTIVE;
		return &if_handle_g;
	}
#endif

	esp_err_t ret = ESP_OK;
	uint16_t prio_q_idx = 0;

	/* Configuration for the SPI bus */
	spi_bus_config_t buscfg={
		.mosi_io_num=GPIO_MOSI,
		.miso_io_num=GPIO_MISO,
		.sclk_io_num=GPIO_SCLK,
		.quadwp_io_num = -1,
		.quadhd_io_num = -1,
		.max_transfer_sz = SPI_BUFFER_SIZE,
#if 0
		/*
		 * Moving ESP32 SPI slave interrupts in flash, Keeping it in IRAM gives crash,
		 * While performing flash erase operation.
		 */
		.intr_flags=ESP_INTR_FLAG_IRAM
#endif
	};

	/* Configuration for the SPI slave interface */
	spi_slave_interface_config_t slvcfg={
		.mode=ESP_SPI_MODE,
		.spics_io_num=GPIO_CS,
		.queue_size=SPI_QUEUE_SIZE,
		.flags=0,
		.post_setup_cb=spi_post_setup_cb,
		.post_trans_cb=spi_post_trans_cb
	};

	if (!hosted_constructs_init_done) {
		/* Configuration for the handshake line */
		gpio_config_t io_conf={
			.intr_type=GPIO_INTR_DISABLE,
			.mode=GPIO_MODE_OUTPUT,
			.pin_bit_mask=GPIO_MASK_HANDSHAKE
		};

		/* Configuration for data_ready line */
		gpio_config_t io_data_ready_conf={
			.intr_type=GPIO_INTR_DISABLE,
			.mode=GPIO_MODE_OUTPUT,
			.pin_bit_mask=GPIO_MASK_DATA_READY
		};

		spi_mempool_create();

		/* Configure handshake and data_ready lines as output */
		gpio_config(&io_conf);
		gpio_config(&io_data_ready_conf);
		reset_handshake_gpio();
		reset_dataready_gpio();

		/* Enable pull-ups on SPI lines
		 * so that no rogue pulses when no master is connected
		 */
		gpio_set_pull_mode(CONFIG_ESP_SPI_GPIO_HANDSHAKE, H_HS_PULL_REGISTER);
		gpio_set_pull_mode(CONFIG_ESP_SPI_GPIO_DATA_READY, H_DR_PULL_REGISTER);
		gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY);
		gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY);
		gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);

		ESP_LOGI(TAG, "SPI Ctrl:%u mode: %u, Freq:ConfigAtHost\nGPIOs: CLK:%u MOSI:%u MISO:%u CS:%u HS:%u DR:%u\n",
				ESP_SPI_CONTROLLER, slvcfg.mode,
				GPIO_SCLK, GPIO_MOSI, GPIO_MISO, GPIO_CS,
				GPIO_HANDSHAKE, GPIO_DATA_READY);

		ESP_LOGI(TAG, "Hosted SPI queue size: Tx:%u Rx:%u", SPI_TX_QUEUE_SIZE, SPI_RX_QUEUE_SIZE);
		register_hs_disable_pin(GPIO_CS);

#if !H_HANDSHAKE_ACTIVE_HIGH
		ESP_LOGI(TAG, "Handshake: Active Low");
#endif

#if !H_DATAREADY_ACTIVE_HIGH
		ESP_LOGI(TAG, "DataReady: Active Low");
#endif
	}


	/* Initialize SPI slave interface */
	ret=spi_slave_initialize(ESP_SPI_CONTROLLER, &buscfg, &slvcfg, DMA_CHAN);
	assert(ret==ESP_OK);

	if (!hosted_constructs_init_done) {
		//gpio_set_drive_capability(CONFIG_ESP_SPI_GPIO_HANDSHAKE, GPIO_DRIVE_CAP_3);
		//gpio_set_drive_capability(CONFIG_ESP_SPI_GPIO_DATA_READY, GPIO_DRIVE_CAP_3);
		gpio_set_drive_capability(GPIO_SCLK, GPIO_DRIVE_CAP_3);
		gpio_set_drive_capability(GPIO_MISO, GPIO_DRIVE_CAP_3);
		gpio_set_pull_mode(GPIO_MISO, GPIO_PULLDOWN_ONLY);

#if HS_DEASSERT_ON_CS
		wait_cs_deassert_sem = xSemaphoreCreateBinary();
		assert(wait_cs_deassert_sem!= NULL);
		ret = xSemaphoreTake(wait_cs_deassert_sem, 0);
#endif
	}

	memset(&if_handle_g, 0, sizeof(if_handle_g));
	if_handle_g.state = ACTIVE;

	if (!hosted_constructs_init_done) {
		spi_tx_sem = xSemaphoreCreateCounting(SPI_TX_QUEUE_SIZE*3, 0);
		assert(spi_tx_sem != NULL);
		spi_rx_sem = xSemaphoreCreateCounting(SPI_RX_QUEUE_SIZE*3, 0);
		assert(spi_rx_sem != NULL);

		for (prio_q_idx=0; prio_q_idx<MAX_PRIORITY_QUEUES;prio_q_idx++) {
			spi_rx_queue[prio_q_idx] = xQueueCreate(SPI_RX_QUEUE_SIZE, sizeof(interface_buffer_handle_t));
			assert(spi_rx_queue[prio_q_idx] != NULL);

			spi_tx_queue[prio_q_idx] = xQueueCreate(SPI_TX_QUEUE_SIZE, sizeof(interface_buffer_handle_t));
			assert(spi_tx_queue[prio_q_idx] != NULL);
		}

		assert(xTaskCreate(spi_transaction_post_process_task , "spi_post_process_task" ,
					CONFIG_ESP_HOSTED_DEFAULT_TASK_STACK_SIZE, NULL,
					CONFIG_ESP_HOSTED_DEFAULT_TASK_PRIORITY, NULL) == pdTRUE);
		hosted_constructs_init_done = 1;
	}


	return &if_handle_g;
}

static int32_t esp_spi_write(interface_handle_t *handle, interface_buffer_handle_t *buf_handle)
{
	int32_t total_len = 0;
	uint16_t offset = 0;
	struct esp_payload_header *header = NULL;
	interface_buffer_handle_t tx_buf_handle = {0};

	if (unlikely(handle->state < ACTIVE)) {
		ESP_LOGE(TAG, "SPI is not active\n");
		return ESP_FAIL;
	}

	if (unlikely(!handle || !buf_handle)) {
		ESP_LOGE(TAG , "Invalid arguments\n");
		return ESP_FAIL;
	}

	if (unlikely(!buf_handle->payload_len || !buf_handle->payload)) {
		ESP_LOGE(TAG , "Invalid arguments, len:%d\n", buf_handle->payload_len);
		return ESP_FAIL;
	}

	total_len = buf_handle->payload_len + sizeof (struct esp_payload_header);

	/* make the adresses dma aligned */
	if (!IS_SPI_DMA_ALIGNED(total_len)) {
		MAKE_SPI_DMA_ALIGNED(total_len);
	}

	if (unlikely(total_len > SPI_BUFFER_SIZE)) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
		ESP_LOGE(TAG, "Max frame length exceeded %ld.. drop it\n", total_len);
#else
		ESP_LOGE(TAG, "Max frame length exceeded %d.. drop it\n", total_len);
#endif
		return ESP_FAIL;
	}

	tx_buf_handle.if_type = buf_handle->if_type;
	tx_buf_handle.if_num = buf_handle->if_num;
	tx_buf_handle.payload_len = total_len;

	tx_buf_handle.payload = spi_buffer_tx_alloc(MEMSET_REQUIRED);
	assert(tx_buf_handle.payload);

	header = (struct esp_payload_header *) tx_buf_handle.payload;

	memset (header, 0, sizeof(struct esp_payload_header));

	/* Initialize header */
	header->if_type = buf_handle->if_type;
	header->if_num = buf_handle->if_num;
	header->len = htole16(buf_handle->payload_len);
	offset = sizeof(struct esp_payload_header);
	header->offset = htole16(offset);
	header->seq_num = htole16(buf_handle->seq_num);
	header->flags = buf_handle->flag;

	tx_buf_handle.wifi_flow_ctrl_en = find_wifi_tx_throttling_to_be_set();

	/* copy the data from caller */
	memcpy(tx_buf_handle.payload + offset, buf_handle->payload, buf_handle->payload_len);


#if CONFIG_ESP_SPI_CHECKSUM
	header->checksum = htole16(compute_checksum(tx_buf_handle.payload,
				offset+buf_handle->payload_len));
#endif

	if (header->if_type == ESP_SERIAL_IF)
		xQueueSend(spi_tx_queue[PRIO_Q_SERIAL], &tx_buf_handle, portMAX_DELAY);
	else if (header->if_type == ESP_HCI_IF)
		xQueueSend(spi_tx_queue[PRIO_Q_BT], &tx_buf_handle, portMAX_DELAY);
	else
		xQueueSend(spi_tx_queue[PRIO_Q_OTHERS], &tx_buf_handle, portMAX_DELAY);

	/* indicate waiting data on ready pin */
	set_dataready_gpio();

	xSemaphoreGive(spi_tx_sem);

	return buf_handle->payload_len;
}

static void IRAM_ATTR esp_spi_read_done(void *handle)
{
	spi_buffer_rx_free(handle);
}

static int esp_spi_read(interface_handle_t *if_handle, interface_buffer_handle_t *buf_handle)
{
	if (unlikely(!if_handle)) {
		ESP_LOGE(TAG, "Invalid arguments to esp_spi_read\n");
		return ESP_FAIL;
	}

	if (likely(spi_rx_sem)) {
		xSemaphoreTake(spi_rx_sem, portMAX_DELAY);
	}
	if (unlikely(if_handle->state < DEACTIVE)) {
		ESP_LOGE(TAG, "spi slave bus inactive\n");
		return ESP_FAIL;
	}

	if (pdFALSE == xQueueReceive(spi_rx_queue[PRIO_Q_SERIAL], buf_handle, 0))
		if (pdFALSE == xQueueReceive(spi_rx_queue[PRIO_Q_BT], buf_handle, 0))
			if (pdFALSE == xQueueReceive(spi_rx_queue[PRIO_Q_OTHERS], buf_handle, 0)) {
				ESP_LOGI(TAG, "%s No element in rx queue", __func__);
		return ESP_FAIL;
	}

	return buf_handle->payload_len;
}

static esp_err_t esp_spi_reset(interface_handle_t *handle)
{
	esp_err_t ret = ESP_OK;
	ret = spi_slave_free(ESP_SPI_CONTROLLER);
	if (ESP_OK != ret) {
		ESP_LOGE(TAG, "spi slave bus free failed\n");
	}
	return ret;
}

static void esp_spi_deinit(interface_handle_t *handle)
{
	if (!handle) {
		return;
	}
#if H_PS_UNLOAD_BUS_WHILE_PS
	if (if_handle_g.state == DEINIT) {
		ESP_LOGW(TAG, "SPI already deinitialized");
		return;
	}
  #if H_IF_AVAILABLE_SPI_SLAVE_ENABLE_DISABLE
	spi_slave_disable(ESP_SPI_CONTROLLER);
  #endif
	handle->state = DEINIT;
	ESP_LOGI(TAG, "SPI deinit requested. Signaling spi task to exit.");
#endif
}
